import {reactive, watch} from "vue";
import {defStoreKey, forEachValue, isPromise} from "./utils";
import ModuleCollection from "./module/module-collection";

function getNestedState(state, path) {
  return path.reduce((prevState, key) => prevState[key], state);
}

function installModule(store, rootState, module, path = []) {
  const isRoot = path.length === 0;
  const namespaced = store._modules.getNamespace(path);
  // console.log('namespaced', namespaced);
  if (!isRoot) {
    const parentState = path.slice(0, -1).reduce((state, key) => state[key], rootState);
    store.withCommiting(() => {
      parentState[path[path.length - 1]] = module.state;
    });
  }
  module.forEachGetter((getter, key) => {
    store._wrapGetters[namespaced + key] = () => getter(getNestedState(store.state, path));
  });
  module.forEachMutation((mutation, key) => {
    const entry = store._mutations[namespaced + key] || (store._mutations[namespaced + key] = []);
    entry.push(payload => {
      mutation.call(store, getNestedState(store.state, path), payload);
    });
  });
  module.forEachAction((action, key) => {
    const entry = store._actions[namespaced + key] || (store._actions[key] = []);
    entry.push(payload => {
      const res = action.call(store, store, payload);
      if (!isPromise(res)) {
        return Promise.resolve(res);
      }
      return res;
    });
  });
  module.forEachChild((child, key) => {
    installModule(store, rootState, child, path.concat(key));
  });
}

function resetStoreState(store, state) {
  store._state = reactive({ data: state });
  store.getters = {};
  forEachValue(store._wrapGetters, (getter, key) => {
    Object.defineProperty(store.getters, key, {
      get: () => getter(),
      enumerable: true
    });
  });
  if (store.strict) {
    enableStrictMode(store);
  }
}

function enableStrictMode(store) {
  watch(() => store._state.data, () => {
    console.assert(store._commiting, '不能再mutate外改变state');
  }, { deep: true, flush: 'sync' }); // 改成同步监听，状态一改变就立即执行回调
}

export default class Store {
  constructor(options) {
    // 格式化options
    this._modules = new ModuleCollection(options);
    // console.log('_modules', this._modules);
    this._wrapGetters = Object.create(null);
    this._mutations = Object.create(null);
    this._actions = Object.create(null);

    this.strict = options.strict || false;
    this._commiting = false;

    const state = this._modules.root.state;
    installModule(this, state, this._modules.root);
    resetStoreState(this, state);
    // console.log('store', this);
    this._subscribers = [];
    if (options.plugins) {
      options.plugins.forEach(plugin => plugin(this));
    }
  }
  subscribe(fn) {
    this._subscribers.push(fn);
  }
  withCommiting(fn) {
    const commiting = this._commiting;
    this._commiting = true;
    fn();
    this._commiting = commiting;
  }
  replaceState(newState) {
    // 严格模式下不能直接改状态
    this.withCommiting(() => {
      this._state.data = newState;
    });
  }
  registerModule(path, rawModule) {
    path = Array.isArray(path) ? path : [path];
    const newModule = this._modules.register(rawModule, path);
    installModule(this, this.state, newModule, path);
    resetStoreState(this, this.state);
  }
  get state() {
    return this._state.data;
  }
  commit = (type, payload) => {
    const entry = this._mutations[type] || [];
    if (entry.length) {
      this.withCommiting(() => {
        entry.forEach(handler => handler(payload));
      });
    }
    if (this._subscribers.length) {
      this._subscribers.forEach(fn => fn({ type, payload }, this.state));
    }
  }
  dispatch(type, payload) {
    const entry = this._actions[type] || [];
    if (entry.length) {
      return Promise.all(entry.map(handler => handler(payload)));
    }
    return Promise.resolve();
  }
  install(app, injectKey = defStoreKey) {
    app.config.globalProperties.$store = this;
    app.provide(injectKey, this);
  }
}
